#include "ThreadPool.h"

namespace GPUCC
{
	namespace CPU
	{
		ThreadPool::ThreadPool() : m_IsRunning(false), m_DidJoin(false), m_NumberOfWorkers(0), m_ThreadLogic(*this)
		{
		}

		ThreadPool::~ThreadPool()
		{
		}

		bool ThreadPool::Start(unsigned int NumberOfWorkers)
		{
			bool Started = false;

			if ((NumberOfWorkers > 0) && (!this->m_IsRunning))
			{
				this->m_DidJoin = false;
				this->m_NumberOfWorkers = NumberOfWorkers;
												
				Started = this->StartWorkerThreads(this->m_NumberOfWorkers);

				if (!Started)
				{
					this->m_NumberOfWorkers = 0;
				}
				else
				{
					this->m_IsRunning = true;
				}
			}

			return (Started);
		}

		bool ThreadPool::StartAndJoin(unsigned int NumberOfAdditionalWorkers)
		{
			bool Started = false;

			if (!this->m_IsRunning)
			{
				this->m_DidJoin = true;
				this->m_NumberOfWorkers = NumberOfAdditionalWorkers;

				Started = this->StartWorkerThreads(this->m_NumberOfWorkers);

				if (!Started)
				{
					this->m_DidJoin = false;
					this->m_NumberOfWorkers = 0;
				}
			}

			if (Started)
			{
				this->m_ThreadLogic.Run();

				this->WaitForWorkers();

				this->m_IsRunning = false;
			}

			return (Started);
		}

		void ThreadPool::Stop()
		{
			if (this->m_IsRunning)
			{
				for (std::vector<std::shared_ptr<Thread>>::iterator CurrentThread = this->m_Threads.begin(); CurrentThread != this->m_Threads.end(); ++CurrentThread)
				{
					(*CurrentThread)->Stop();
				}

				this->m_ThreadLogic.Stop();

				if (!this->m_DidJoin)
				{
					this->m_IsRunning = false;
				}
			}
		}

		void ThreadPool::StopAndWait()
		{
			if ((this->m_IsRunning) && (!this->m_DidJoin))
			{
				for (std::vector<std::shared_ptr<Thread>>::iterator CurrentThread = this->m_Threads.begin(); CurrentThread != this->m_Threads.end(); ++CurrentThread)
				{
					(*CurrentThread)->Stop();
				}
				
				this->WaitForWorkers();

				this->m_IsRunning = false;
			}
		}
			
		bool ThreadPool::IsRunning()
		{
			return (this->m_IsRunning);
		}

		bool ThreadPool::DidJoin()
		{
			return (this->m_DidJoin);
		}

		unsigned int ThreadPool::GetNumberOfWrokers()
		{
			return (this->m_NumberOfWorkers);
		}

		void ThreadPool::AddJobExecution(std::shared_ptr<IThreadPoolWorkerJob> JobToExecute)
		{
			this->m_PendingJobs.Push(JobToExecute);
		}

		bool ThreadPool::StartWorkerThreads(unsigned int NumberOfWorkers)
		{
			bool Started = false;

			this->m_Threads.clear();

			for (unsigned int WorkerIndex = 0; WorkerIndex < this->m_NumberOfWorkers; ++WorkerIndex)
			{
				this->m_Threads.push_back(std::make_shared<Thread>(std::make_shared<ThreadPoolWorkerThreadLogic>(*this)));
			}

			Started = true;
			
			for (unsigned int WorkerIndex = 0; ((WorkerIndex < this->m_NumberOfWorkers) && (Started)); ++WorkerIndex)
			{
				Started = this->m_Threads[WorkerIndex]->Start();
			}

			if (!Started)
			{
				for (unsigned int WorkerIndex = 0; ((WorkerIndex < this->m_NumberOfWorkers) && (Started)); ++WorkerIndex)
				{
					this->m_Threads[WorkerIndex]->StopAndWait();
				}
				
				this->m_Threads.clear();	
			}

			return (Started);
		}

		void ThreadPool::WaitForWorkers()
		{
			for (std::vector<std::shared_ptr<Thread>>::iterator CurrentThread = this->m_Threads.begin(); CurrentThread != this->m_Threads.end(); ++CurrentThread)
			{
				(*CurrentThread)->Join();
			}
		}

		ThreadPool::ThreadPoolWorkerThreadLogic::ThreadPoolWorkerThreadLogic(ThreadPool& Instance) : m_Instance(Instance)
		{
		}

		ThreadPool::ThreadPoolWorkerThreadLogic::~ThreadPoolWorkerThreadLogic()
		{
		}

		void ThreadPool::ThreadPoolWorkerThreadLogic::Run()
		{
			bool Running = true;

			do
			{
				std::shared_ptr<IThreadPoolWorkerJob> CurrentJob = this->m_Instance.m_PendingJobs.Pop();
				
				if (CurrentJob.get() == NULL)
				{
					Running = false;
				}
				else
				{
					CurrentJob->Run();

					CurrentJob = NULL;
				}

			} while (Running);
		}
		
		void ThreadPool::ThreadPoolWorkerThreadLogic::Stop()
		{
			this->m_Instance.AddJobExecution(NULL);
		}
	}
}